﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Configuration;

namespace TextFlow.Engine
{
    internal static class FunctionGenerator
    {
        private static Dictionary<string, Expression> Methods = new Dictionary<string, Expression>();
        private static Dictionary<string, Expression> Properties = new Dictionary<string, Expression>();

        public static Expression GenerateMethodCall( string service, string method, IEnumerable<Expression> parms )
        {
            if ( string.IsNullOrEmpty( service ) )
            {
                throw new ArgumentNullException( "service" );
            }

            if ( string.IsNullOrEmpty( method ) )
            {
                throw new ArgumentNullException( "method" );
            }

            string key = string.Format( "{0}_{1}", service, method );

            if ( Methods.ContainsKey( key ) )
            {
                return Methods[ key ];
            }
            else
            {
                Type svcType = GetServiceType( service );

                var types = from parm in parms select parm.Type;

                Debug.Assert( types != null );
                Debug.Assert( types.Count() == parms.Count() );

                MethodInfo mi = svcType.GetMethod( method, types.ToArray() );

                if ( mi == null )
                {
                    throw new InvalidOperationException( string.Format( "No method {0} exists for configured service {1}.", method, service ) );
                }
                else if ( mi.ReturnType == typeof( void ) )
                {
                    throw new InvalidOperationException( string.Format( "Method {0} on configured service {1} has no return type; this is not supported.", method, service ) );
                }

                Expression methodCall = Expression.Call( Expression.Parameter( svcType, key ), mi, parms );

                Methods.Add( key, methodCall );

                return methodCall;
            }
        }

        public static Expression GeneratePropertyLookup( string service, string property )
        {
            if ( string.IsNullOrEmpty( service ) )
            {
                throw new ArgumentNullException( "service" );
            }

            if ( string.IsNullOrEmpty( property ) )
            {
                throw new ArgumentNullException( "property" );
            }

            string key = string.Format( "{0}_{1}", service, property );

            if ( Properties.ContainsKey( key ) )
            {
                return Properties[ key ];
            }
            else
            {
                Type svcType = GetServiceType( service );

                PropertyInfo pi = svcType.GetProperty( property );

                if ( pi == null )
                {
                    throw new InvalidOperationException( string.Format( "No property {0} exists for configured service {1}.", property, service ) );
                }

                Expression methodCall = Expression.Property( Expression.Parameter( svcType, key ), pi.GetGetMethod() );

                Properties.Add( key, methodCall );

                return methodCall;
            }
        }

        private static Type GetServiceType( string service )
        {
            Debug.Assert( string.IsNullOrEmpty( service ) == false );

            Hashtable fromConfig = ConfigurationManager.GetSection( "TextFlowFunctions" ) as Hashtable;

            if ( fromConfig == null )
            {
                throw new InvalidOperationException( "'TextFlowFunctions' config section not found in application config file." );
            }

            string svcTypeName = fromConfig[ service ] as string;

            if ( string.IsNullOrEmpty( svcTypeName ) )
            {
                throw new InvalidOperationException( string.Format( "'TextFlowFunctions' config section does not contain '{0}' key.", service ) );
            }

            Type svcType = Type.GetType( svcTypeName );

            if ( svcType == null )
            {
                throw new InvalidOperationException( string.Format( "{0} type cannot be loaded.", svcTypeName ) );
            }

            return svcType;
        }
    }
}
